﻿using System;
using IrcDotNet;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Threading;
using System.Collections;

namespace IrcDotNet.Service
{
	public class Channel : INotifyPropertyChanged
	{
		#region Private Members
		/// <summary>
		/// The IrcClient used to connect to the channel.
		/// </summary>
		IrcClient client { get; set; }
		/// <summary>
		/// The IrcChannel used for send or receive message.
		/// </summary>
		IrcChannel channel { get; set; }
		#endregion

		#region Public Properties
		private string name;
		/// <summary>
		/// Name of the current Channel.
		/// </summary>
		public string Name
		{
			get { return name; }
			private set
			{
				if (value != this.name)
				{
					this.name = value;
					NotifyPropertyChanged("Name");
				}
			}
		}

		private bool isConnected;
		/// <summary>
		/// Is the Channel currently connected.
		/// </summary>
		public bool IsConnected
		{
			get { return isConnected; }
			private set
			{
				if (value != this.isConnected)
				{
					this.isConnected = value;
					NotifyPropertyChanged("IsConnected");
				}
			}
		}

		/// <summary>
		/// Messages received from the IrcChannel.
		/// </summary>
		public ObservableCollection<Tuple<string, string>> Messages { get; private set; }
		#endregion

		#region Public Method
		public Channel(IrcClient server)
		{
			this.client = server;
			this.isConnected = false;
		}

		/// <summary>
		/// Connect to a channel.
		/// </summary>
		/// <param name="name">Name of the channel, it need '#' in front of it.</param>
		/// <returns>True if the connection to the channel is succesfull,
		/// False if you get a timeout.</returns>
		public bool Join(string name)
		{
			if (!name.StartsWith("#"))
			{
				name = '#' + name;
			}

			using (var connectionEvent = new ManualResetEvent(false))
			{
				EventHandler<IrcChannelEventArgs> connected = (s, e) =>
				{
					if (e.Channel.Name == name)
					{
						this.channel = e.Channel;
						connectionEvent.Set();
					}
				};
				client.LocalUser.JoinedChannel += connected;

				client.Channels.Join(name);

				if (!connectionEvent.WaitOne(10000))
				{
					client.LocalUser.JoinedChannel -= connected;
					return false;
				}
				else
				{
					client.LocalUser.JoinedChannel -= connected;
					IsConnected = true;
					Name = this.channel.Name;
					Messages = new ObservableCollection<Tuple<string, string>>();
					this.channel.MessageReceived += messageReceived;
					return true;
				}
			}
		}

		/// <summary>
		/// Leave the channel.
		/// </summary>
		/// <param name="comment">This comment can be null.</param>
		/// <returns>True if the disconnexion get acknowledge by the server,
		/// False otherwise.</returns>
		public bool Leave(string comment)
		{
			using (var disconnectionEvent = new ManualResetEvent(false))
			{
				EventHandler<IrcChannelEventArgs> disconnected = (s, e) =>
				{ if (e.Channel.Name == this.Name) disconnectionEvent.Set(); };
				client.LocalUser.LeftChannel += disconnected;

				channel.Leave(comment);

				if (!disconnectionEvent.WaitOne(10000))
				{
					client.LocalUser.LeftChannel -= disconnected;
					return false;
				}
				else
				{
					client.LocalUser.LeftChannel -= disconnected;
					IsConnected = false;
					Name = String.Empty;
					channel.MessageReceived -= messageReceived;
					return true;
				}
			}
		}

		/// <summary>
		/// Send a message to the currently connected channel.
		/// </summary>
		/// <param name="message"></param>
		public void Say(string message)
		{
			client.LocalUser.SendMessage(channel, message);
		}

		/// <summary>
		/// Get the nickname of users of this channel.
		/// </summary>
		/// <returns></returns>
		public IEnumerable ListUser()
		{
			foreach (IrcChannelUser user in channel.Users)
			{
				yield return user.User.NickName;
			}
		}
		#endregion

		#region Private Method
		void NotifyPropertyChanged(String info)
		{
			if (PropertyChanged != null)
			{
				PropertyChanged(this, new PropertyChangedEventArgs(info));
			}
		}

		/// <summary>
		/// When the IrcChannel receive a message, we add it to the received messages of this Channel.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void messageReceived(object sender, IrcMessageEventArgs e)
		{
			this.Messages.Add(new Tuple<string, string>(e.Source.Name, e.Text));
		}
		#endregion

		#region Public Events
		public event PropertyChangedEventHandler PropertyChanged;
		#endregion
	}
}
